# -*- coding: utf-8 -*-
"""
@Time ： 2025/8/27 11:21
@Auth ： 李天天
"""
# base_page.py
import os
import time
import json
from datetime import datetime
from typing import Optional, Union, List, Dict, Any
from playwright.sync_api import Page, expect, Locator, TimeoutError as PlaywrightTimeoutError
from Common.handle_log import do_log
from Common.handle_path import SCREENSHOT_DIR

class BasePage:
    """
    基于Playwright的基础页面类，提供常用的页面操作方法
    """

    def __init__(self, page: Page):
        """
        初始化BasePage
        :param page: Playwright的Page对象
        """
        self.page = page
        self.logger = do_log
        self.default_timeout = 10000  # 默认超时时间10秒
        
    def get_locator(self, selector: str) -> Locator:
        """
        获取元素定位器
        :param selector: 元素选择器
        :return: Locator对象
        """
        return self.page.locator(selector)
    
    def click_element(self, selector: str, timeout: Optional[int] = None, force: bool = False, 
                     img_desc: Optional[str] = None, auto_screenshot: bool = True) -> bool:
        """
        点击元素
        :param selector: 元素选择器
        :param timeout: 超时时间，默认使用self.default_timeout
        :param force: 是否强制点击
        :param img_desc: 截图描述，失败时用于截图文件命名
        :param auto_screenshot: 失败时是否自动截图
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            locator.click(timeout=timeout, force=force)
            self.logger.info(f"成功点击元素: {selector}")
            return True
        except Exception as e:
            self.logger.error(f"点击元素失败 {selector}: {str(e)}")
            if auto_screenshot:
                desc = img_desc or f"click_element_failed_{selector.replace(' ', '_').replace('>', '_')}"
                self.save_img(desc)
            return False
    
    def double_click_element(self, selector: str, timeout: Optional[int] = None, 
                            img_desc: Optional[str] = None, auto_screenshot: bool = True) -> bool:
        """
        双击元素
        :param selector: 元素选择器
        :param timeout: 超时时间
        :param img_desc: 截图描述，失败时用于截图文件命名
        :param auto_screenshot: 失败时是否自动截图
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            locator.dblclick(timeout=timeout)
            self.logger.info(f"成功双击元素: {selector}")
            return True
        except Exception as e:
            self.logger.error(f"双击元素失败 {selector}: {str(e)}")
            if auto_screenshot:
                desc = img_desc or f"double_click_failed_{selector.replace(' ', '_').replace('>', '_')}"
                self.save_img(desc)
            return False
    
    def right_click_element(self, selector: str, timeout: Optional[int] = None, 
                           img_desc: Optional[str] = None, auto_screenshot: bool = True) -> bool:
        """
        右键点击元素
        :param selector: 元素选择器
        :param timeout: 超时时间
        :param img_desc: 截图描述，失败时用于截图文件命名
        :param auto_screenshot: 失败时是否自动截图
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            locator.click(button="right", timeout=timeout)
            self.logger.info(f"成功右键点击元素: {selector}")
            return True
        except Exception as e:
            self.logger.error(f"右键点击元素失败 {selector}: {str(e)}")
            if auto_screenshot:
                desc = img_desc or f"right_click_failed_{selector.replace(' ', '_').replace('>', '_')}"
                self.save_img(desc)
            return False
    
    def fill_text(self, selector: str, text: str, timeout: Optional[int] = None, clear_first: bool = True, 
                 img_desc: Optional[str] = None, auto_screenshot: bool = True) -> bool:
        """
        输入文本
        :param selector: 元素选择器
        :param text: 要输入的文本
        :param timeout: 超时时间
        :param clear_first: 是否先清空输入框
        :param img_desc: 截图描述，失败时用于截图文件命名
        :param auto_screenshot: 失败时是否自动截图
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            if clear_first:
                locator.clear(timeout=timeout)
            locator.fill(text, timeout=timeout)
            self.logger.info(f"成功输入文本到元素 {selector}: {text}")
            return True
        except Exception as e:
            self.logger.error(f"输入文本失败 {selector}: {str(e)}")
            if auto_screenshot:
                desc = img_desc or f"fill_text_failed_{selector.replace(' ', '_').replace('>', '_')}"
                self.save_img(desc)
            return False
    
    def type_text(self, selector: str, text: str, delay: int = 100, timeout: Optional[int] = None) -> bool:
        """
        逐字符输入文本（模拟真实用户输入）
        :param selector: 元素选择器
        :param text: 要输入的文本
        :param delay: 每个字符之间的延迟（毫秒）
        :param timeout: 超时时间
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            locator.type(text, delay=delay, timeout=timeout)
            self.logger.info(f"成功逐字符输入文本到元素 {selector}: {text}")
            return True
        except Exception as e:
            self.logger.error(f"逐字符输入文本失败 {selector}: {str(e)}")
            return False
    
    def get_text(self, selector: str, timeout: Optional[int] = None) -> Optional[str]:
        """
        获取元素文本
        :param selector: 元素选择器
        :param timeout: 超时时间
        :return: 元素文本，失败返回None
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            text = locator.text_content(timeout=timeout)
            self.logger.info(f"成功获取元素文本 {selector}: {text}")
            return text
        except Exception as e:
            self.logger.error(f"获取元素文本失败 {selector}: {str(e)}")
            return None
    
    def get_inner_text(self, selector: str, timeout: Optional[int] = None) -> Optional[str]:
        """
        获取元素inner text
        :param selector: 元素选择器
        :param timeout: 超时时间
        :return: 元素inner text，失败返回None
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            text = locator.inner_text(timeout=timeout)
            self.logger.info(f"成功获取元素inner text {selector}: {text}")
            return text
        except Exception as e:
            self.logger.error(f"获取元素inner text失败 {selector}: {str(e)}")
            return None
    
    def get_attribute(self, selector: str, attribute: str, timeout: Optional[int] = None) -> Optional[str]:
        """
        获取元素属性值
        :param selector: 元素选择器
        :param attribute: 属性名
        :param timeout: 超时时间
        :return: 属性值，失败返回None
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            value = locator.get_attribute(attribute, timeout=timeout)
            self.logger.info(f"成功获取元素属性 {selector}.{attribute}: {value}")
            return value
        except Exception as e:
            self.logger.error(f"获取元素属性失败 {selector}.{attribute}: {str(e)}")
            return None
    
    def get_input_value(self, selector: str, timeout: Optional[int] = None) -> Optional[str]:
        """
        获取输入框的值
        :param selector: 元素选择器
        :param timeout: 超时时间
        :return: 输入框的值，失败返回None
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            value = locator.input_value(timeout=timeout)
            self.logger.info(f"成功获取输入框值 {selector}: {value}")
            return value
        except Exception as e:
            self.logger.error(f"获取输入框值失败 {selector}: {str(e)}")
            return None
    
    def select_option(self, selector: str, value: Union[str, List[str]], timeout: Optional[int] = None, 
                     img_desc: Optional[str] = None, auto_screenshot: bool = True) -> bool:
        """
        选择下拉框选项
        :param selector: 下拉框选择器
        :param value: 要选择的值
        :param timeout: 超时时间
        :param img_desc: 截图描述，失败时用于截图文件命名
        :param auto_screenshot: 失败时是否自动截图
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            locator.select_option(value, timeout=timeout)
            self.logger.info(f"成功选择下拉选项 {selector}: {value}")
            return True
        except Exception as e:
            self.logger.error(f"选择下拉选项失败 {selector}: {str(e)}")
            if auto_screenshot:
                desc = img_desc or f"select_option_failed_{selector.replace(' ', '_').replace('>', '_')}"
                self.save_img(desc)
            return False
    
    def check_checkbox(self, selector: str, timeout: Optional[int] = None, 
                      img_desc: Optional[str] = None, auto_screenshot: bool = True) -> bool:
        """
        选中复选框
        :param selector: 复选框选择器
        :param timeout: 超时时间
        :param img_desc: 截图描述，失败时用于截图文件命名
        :param auto_screenshot: 失败时是否自动截图
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            locator.check(timeout=timeout)
            self.logger.info(f"成功选中复选框: {selector}")
            return True
        except Exception as e:
            self.logger.error(f"选中复选框失败 {selector}: {str(e)}")
            if auto_screenshot:
                desc = img_desc or f"check_checkbox_failed_{selector.replace(' ', '_').replace('>', '_')}"
                self.save_img(desc)
            return False
    
    def uncheck_checkbox(self, selector: str, timeout: Optional[int] = None) -> bool:
        """
        取消选中复选框
        :param selector: 复选框选择器
        :param timeout: 超时时间
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            locator.uncheck(timeout=timeout)
            self.logger.info(f"成功取消选中复选框: {selector}")
            return True
        except Exception as e:
            self.logger.error(f"取消选中复选框失败 {selector}: {str(e)}")
            return False
    
    def is_checked(self, selector: str, timeout: Optional[int] = None) -> bool:
        """
        检查复选框是否被选中
        :param selector: 复选框选择器
        :param timeout: 超时时间
        :return: 是否被选中
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            checked = locator.is_checked(timeout=timeout)
            self.logger.info(f"复选框 {selector} 选中状态: {checked}")
            return checked
        except Exception as e:
            self.logger.error(f"检查复选框状态失败 {selector}: {str(e)}")
            return False
    
    def hover_element(self, selector: str, timeout: Optional[int] = None, 
                     img_desc: Optional[str] = None, auto_screenshot: bool = True) -> bool:
        """
        鼠标悬停在元素上
        :param selector: 元素选择器
        :param timeout: 超时时间
        :param img_desc: 截图描述，失败时用于截图文件命名
        :param auto_screenshot: 失败时是否自动截图
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            locator.hover(timeout=timeout)
            self.logger.info(f"成功悬停在元素上: {selector}")
            return True
        except Exception as e:
            self.logger.error(f"悬停在元素上失败 {selector}: {str(e)}")
            if auto_screenshot:
                desc = img_desc or f"hover_element_failed_{selector.replace(' ', '_').replace('>', '_')}"
                self.save_img(desc)
            return False
    
    def drag_and_drop(self, source_selector: str, target_selector: str, timeout: Optional[int] = None) -> bool:
        """
        拖拽元素
        :param source_selector: 源元素选择器
        :param target_selector: 目标元素选择器
        :param timeout: 超时时间
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            source_locator = self.get_locator(source_selector)
            target_locator = self.get_locator(target_selector)
            source_locator.drag_to(target_locator, timeout=timeout)
            self.logger.info(f"成功拖拽元素从 {source_selector} 到 {target_selector}")
            return True
        except Exception as e:
            self.logger.error(f"拖拽元素失败 {source_selector} -> {target_selector}: {str(e)}")
            return False
    
    # ================== 等待机制相关方法 ==================
    
    def wait_for_element_visible(self, selector: str, timeout: Optional[int] = None) -> bool:
        """
        等待元素可见
        :param selector: 元素选择器
        :param timeout: 超时时间
        :return: 等待是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            locator.wait_for(state="visible", timeout=timeout)
            self.logger.info(f"元素变为可见: {selector}")
            return True
        except Exception as e:
            self.logger.error(f"等待元素可见失败 {selector}: {str(e)}")
            return False
    
    def wait_for_element_hidden(self, selector: str, timeout: Optional[int] = None) -> bool:
        """
        等待元素隐藏
        :param selector: 元素选择器
        :param timeout: 超时时间
        :return: 等待是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            locator.wait_for(state="hidden", timeout=timeout)
            self.logger.info(f"元素变为隐藏: {selector}")
            return True
        except Exception as e:
            self.logger.error(f"等待元素隐藏失败 {selector}: {str(e)}")
            return False
    
    def wait_for_element_attached(self, selector: str, timeout: Optional[int] = None) -> bool:
        """
        等待元素附加到DOM
        :param selector: 元素选择器
        :param timeout: 超时时间
        :return: 等待是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            locator.wait_for(state="attached", timeout=timeout)
            self.logger.info(f"元素附加到DOM: {selector}")
            return True
        except Exception as e:
            self.logger.error(f"等待元素附加到DOM失败 {selector}: {str(e)}")
            return False
    
    def wait_for_element_detached(self, selector: str, timeout: Optional[int] = None) -> bool:
        """
        等待元素从DOM分离
        :param selector: 元素选择器
        :param timeout: 超时时间
        :return: 等待是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            locator.wait_for(state="detached", timeout=timeout)
            self.logger.info(f"元素从DOM分离: {selector}")
            return True
        except Exception as e:
            self.logger.error(f"等待元素从DOM分离失败 {selector}: {str(e)}")
            return False
    
    def wait_for_load_state(self, state: str = "load", timeout: Optional[int] = None) -> bool:
        """
        等待页面加载状态
        :param state: 加载状态 ('load', 'domcontentloaded', 'networkidle')
        :param timeout: 超时时间
        :return: 等待是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            self.page.wait_for_load_state(state, timeout=timeout)
            self.logger.info(f"页面加载状态达到: {state}")
            return True
        except Exception as e:
            self.logger.error(f"等待页面加载状态失败 {state}: {str(e)}")
            return False
    
    def wait_for_url(self, url_pattern: str, timeout: Optional[int] = None) -> bool:
        """
        等待URL匹配指定模式
        :param url_pattern: URL模式（可以是字符串或正则表达式）
        :param timeout: 超时时间
        :return: 等待是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            self.page.wait_for_url(url_pattern, timeout=timeout)
            self.logger.info(f"URL匹配模式: {url_pattern}")
            return True
        except Exception as e:
            self.logger.error(f"等待URL匹配失败 {url_pattern}: {str(e)}")
            return False
    
    def wait_for_timeout(self, timeout: int) -> None:
        """
        等待指定时间
        :param timeout: 等待时间（毫秒）
        """
        self.page.wait_for_timeout(timeout)
        self.logger.info(f"等待 {timeout} 毫秒")
    
    def is_element_visible(self, selector: str, timeout: Optional[int] = None) -> bool:
        """
        检查元素是否可见
        :param selector: 元素选择器
        :param timeout: 超时时间
        :return: 元素是否可见
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            visible = locator.is_visible(timeout=timeout)
            self.logger.info(f"元素 {selector} 可见状态: {visible}")
            return visible
        except Exception as e:
            self.logger.error(f"检查元素可见状态失败 {selector}: {str(e)}")
            return False
    
    def is_element_enabled(self, selector: str, timeout: Optional[int] = None) -> bool:
        """
        检查元素是否启用
        :param selector: 元素选择器
        :param timeout: 超时时间
        :return: 元素是否启用
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            enabled = locator.is_enabled(timeout=timeout)
            self.logger.info(f"元素 {selector} 启用状态: {enabled}")
            return enabled
        except Exception as e:
            self.logger.error(f"检查元素启用状态失败 {selector}: {str(e)}")
            return False
    
    def is_element_editable(self, selector: str, timeout: Optional[int] = None) -> bool:
        """
        检查元素是否可编辑
        :param selector: 元素选择器
        :param timeout: 超时时间
        :return: 元素是否可编辑
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            editable = locator.is_editable(timeout=timeout)
            self.logger.info(f"元素 {selector} 可编辑状态: {editable}")
            return editable
        except Exception as e:
            self.logger.error(f"检查元素可编辑状态失败 {selector}: {str(e)}")
            return False
    
    def get_element_count(self, selector: str) -> int:
        """
        获取匹配选择器的元素数量
        :param selector: 元素选择器
        :return: 元素数量
        """
        try:
            locator = self.get_locator(selector)
            count = locator.count()
            self.logger.info(f"选择器 {selector} 匹配到 {count} 个元素")
            return count
        except Exception as e:
            self.logger.error(f"获取元素数量失败 {selector}: {str(e)}")
            return 0
    
    # ================== 截图相关方法 ==================
    
    def save_img(self, img_description: str) -> Optional[str]:
        """
        保存页面截图（参考安卓版本实现）
        :param img_description: 图片的描述，格式为 页面名称_功能名
        :return: 截图文件路径，失败返回None
        """
        try:
            now = time.strftime("%Y-%m-%d %H_%M_%S")
            file_path = f"{now}_{img_description}.png"
            img_path = os.path.join(SCREENSHOT_DIR, file_path)
            
            # 确保截图目录存在
            if not os.path.exists(SCREENSHOT_DIR):
                os.makedirs(SCREENSHOT_DIR)
            
            # 截图保存
            self.page.screenshot(path=img_path)
            
            self.logger.info(f"截图成功，截图存放在：{img_path}")
            return img_path
            
        except Exception as e:
            self.logger.error(f"截图失败！{str(e)}")
            return None
    
    def save_img_with_selector(self, selector: str, img_description: str, timeout: Optional[int] = None) -> Optional[str]:
        """
        对指定元素进行截图
        :param selector: 元素选择器
        :param img_description: 图片描述
        :param timeout: 超时时间
        :return: 截图文件路径，失败返回None
        """
        try:
            timeout = timeout or self.default_timeout
            now = time.strftime("%Y-%m-%d %H_%M_%S")
            file_path = f"{now}_{img_description}_element.png"
            img_path = os.path.join(SCREENSHOT_DIR, file_path)
            
            # 确保截图目录存在
            if not os.path.exists(SCREENSHOT_DIR):
                os.makedirs(SCREENSHOT_DIR)
            
            # 元素截图
            locator = self.get_locator(selector)
            locator.screenshot(path=img_path, timeout=timeout)
            
            self.logger.info(f"元素截图成功，截图存放在：{img_path}")
            return img_path
            
        except Exception as e:
            self.logger.error(f"元素截图失败！{str(e)}")
            return None
    
    def take_screenshot(self, path: Optional[str] = None, full_page: bool = False, 
                       clip: Optional[Dict[str, Union[int, float]]] = None) -> bool:
        """
        截取页面截图
        :param path: 保存路径，不指定则自动生成
        :param full_page: 是否截取整个页面
        :param clip: 截取区域 {"x": 0, "y": 0, "width": 100, "height": 100}
        :return: 截图是否成功
        """
        try:
            if path is None:
                timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                path = os.path.join(SCREENSHOT_DIR, f"screenshot_{timestamp}.png")
            
            # 确保截图目录存在
            screenshot_dir = os.path.dirname(path)
            if screenshot_dir and not os.path.exists(screenshot_dir):
                os.makedirs(screenshot_dir)
            
            self.page.screenshot(path=path, full_page=full_page, clip=clip)
            self.logger.info(f"成功截取页面截图: {path}")
            return True
        except Exception as e:
            self.logger.error(f"截取页面截图失败: {str(e)}")
            return False
    
    # ================== 扩展操作方法 ==================
    
    def click_element_times(self, selector: str, times: int, timeout: Optional[int] = None, 
                           img_desc: Optional[str] = None, auto_screenshot: bool = True, 
                           interval: float = 0.1) -> bool:
        """
        连续点击元素指定次数
        :param selector: 元素选择器
        :param times: 点击次数
        :param timeout: 超时时间
        :param img_desc: 截图描述，失败时用于截图文件命名
        :param auto_screenshot: 失败时是否自动截图
        :param interval: 每次点击间隔时间（秒）
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            
            for i in range(times):
                locator.click(timeout=timeout)
                if i < times - 1:  # 最后一次点击不需要等待
                    time.sleep(interval)
            
            self.logger.info(f"成功连续点击元素 {times} 次: {selector}")
            return True
        except Exception as e:
            self.logger.error(f"连续点击元素失败 {selector}: {str(e)}")
            if auto_screenshot:
                desc = img_desc or f"click_times_failed_{selector.replace(' ', '_').replace('>', '_')}"
                self.save_img(desc)
            return False
    
    def long_click_element(self, selector: str, hold_time: float = 1.0, timeout: Optional[int] = None,
                          img_desc: Optional[str] = None, auto_screenshot: bool = True) -> bool:
        """
        长按元素
        :param selector: 元素选择器
        :param hold_time: 长按时间（秒）
        :param timeout: 超时时间
        :param img_desc: 截图描述，失败时用于截图文件命名
        :param auto_screenshot: 失败时是否自动截图
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            
            # 使用页面坐标进行长按
            box = locator.bounding_box(timeout=timeout)
            if box:
                x = box['x'] + box['width'] / 2
                y = box['y'] + box['height'] / 2
                
                # 鼠标按下
                self.page.mouse.move(x, y)
                self.page.mouse.down()
                time.sleep(hold_time)
                self.page.mouse.up()
                
                self.logger.info(f"成功长按元素 {hold_time} 秒: {selector}")
                return True
            else:
                raise Exception(f"无法获取元素边界框: {selector}")
                
        except Exception as e:
            self.logger.error(f"长按元素失败 {selector}: {str(e)}")
            if auto_screenshot:
                desc = img_desc or f"long_click_failed_{selector.replace(' ', '_').replace('>', '_')}"
                self.save_img(desc)
            return False
    
    def wait_and_click(self, selector: str, wait_timeout: Optional[int] = None, 
                      click_timeout: Optional[int] = None, img_desc: Optional[str] = None, 
                      auto_screenshot: bool = True) -> bool:
        """
        等待元素可见后点击
        :param selector: 元素选择器
        :param wait_timeout: 等待超时时间
        :param click_timeout: 点击超时时间
        :param img_desc: 截图描述，失败时用于截图文件命名
        :param auto_screenshot: 失败时是否自动截图
        :return: 操作是否成功
        """
        try:
            wait_timeout = wait_timeout or self.default_timeout
            click_timeout = click_timeout or self.default_timeout
            
            # 先等待元素可见
            if self.wait_for_element_visible(selector, wait_timeout):
                # 然后点击
                return self.click_element(selector, click_timeout, img_desc=img_desc, auto_screenshot=auto_screenshot)
            else:
                raise Exception(f"等待元素可见超时: {selector}")
                
        except Exception as e:
            self.logger.error(f"等待并点击元素失败 {selector}: {str(e)}")
            if auto_screenshot:
                desc = img_desc or f"wait_and_click_failed_{selector.replace(' ', '_').replace('>', '_')}"
                self.save_img(desc)
            return False
    
    def scroll_and_click(self, selector: str, timeout: Optional[int] = None, 
                        img_desc: Optional[str] = None, auto_screenshot: bool = True) -> bool:
        """
        滚动到元素并点击
        :param selector: 元素选择器
        :param timeout: 超时时间
        :param img_desc: 截图描述，失败时用于截图文件命名
        :param auto_screenshot: 失败时是否自动截图
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            
            # 滚动到元素
            locator.scroll_into_view_if_needed(timeout=timeout)
            # 等待一下确保滚动完成
            time.sleep(0.5)
            # 点击元素
            locator.click(timeout=timeout)
            
            self.logger.info(f"成功滚动并点击元素: {selector}")
            return True
        except Exception as e:
            self.logger.error(f"滚动并点击元素失败 {selector}: {str(e)}")
            if auto_screenshot:
                desc = img_desc or f"scroll_and_click_failed_{selector.replace(' ', '_').replace('>', '_')}"
                self.save_img(desc)
            return False
    
    def force_click(self, selector: str, timeout: Optional[int] = None, 
                   img_desc: Optional[str] = None, auto_screenshot: bool = True) -> bool:
        """
        强制点击元素（忽略是否可见等限制）
        :param selector: 元素选择器
        :param timeout: 超时时间
        :param img_desc: 截图描述，失败时用于截图文件命名
        :param auto_screenshot: 失败时是否自动截图
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            locator.click(timeout=timeout, force=True)
            self.logger.info(f"成功强制点击元素: {selector}")
            return True
        except Exception as e:
            self.logger.error(f"强制点击元素失败 {selector}: {str(e)}")
            if auto_screenshot:
                desc = img_desc or f"force_click_failed_{selector.replace(' ', '_').replace('>', '_')}"
                self.save_img(desc)
            return False
    
    def get_element_by_text(self, text: str, tag: str = "*") -> Optional[str]:
        """
        根据文本内容获取元素选择器
        :param text: 要查找的文本
        :param tag: 元素标签，默认为所有标签
        :return: 元素选择器，找不到返回None
        """
        try:
            selector = f"{tag}:has-text('{text}')"
            locator = self.get_locator(selector)
            if locator.count() > 0:
                self.logger.info(f"找到包含文本 '{text}' 的元素")
                return selector
            else:
                self.logger.warning(f"未找到包含文本 '{text}' 的元素")
                return None
        except Exception as e:
            self.logger.error(f"根据文本查找元素失败: {str(e)}")
            return None
    
    def click_by_text(self, text: str, tag: str = "*", timeout: Optional[int] = None,
                     img_desc: Optional[str] = None, auto_screenshot: bool = True) -> bool:
        """
        根据文本内容点击元素
        :param text: 要点击的文本
        :param tag: 元素标签，默认为所有标签
        :param timeout: 超时时间
        :param img_desc: 截图描述，失败时用于截图文件命名
        :param auto_screenshot: 失败时是否自动截图
        :return: 操作是否成功
        """
        selector = self.get_element_by_text(text, tag)
        if selector:
            return self.click_element(selector, timeout, img_desc=img_desc, auto_screenshot=auto_screenshot)
        else:
            self.logger.error(f"无法找到包含文本 '{text}' 的元素进行点击")
            if auto_screenshot:
                desc = img_desc or f"click_by_text_failed_{text.replace(' ', '_')}"
                self.save_img(desc)
            return False
    
    # ================== 页面导航相关方法 ==================
    
    def goto(self, url: str, timeout: Optional[int] = None, wait_until: str = "load") -> bool:
        """
        导航到指定URL
        :param url: 目标URL
        :param timeout: 超时时间
        :param wait_until: 等待条件 ('load', 'domcontentloaded', 'networkidle', 'commit')
        :return: 导航是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            self.page.goto(url, timeout=timeout, wait_until=wait_until)
            self.logger.info(f"成功导航到URL: {url}")
            return True
        except Exception as e:
            self.logger.error(f"导航到URL失败 {url}: {str(e)}")
            return False
    
    def reload(self, timeout: Optional[int] = None, wait_until: str = "load") -> bool:
        """
        刷新页面
        :param timeout: 超时时间
        :param wait_until: 等待条件
        :return: 刷新是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            self.page.reload(timeout=timeout, wait_until=wait_until)
            self.logger.info("成功刷新页面")
            return True
        except Exception as e:
            self.logger.error(f"刷新页面失败: {str(e)}")
            return False
    
    def go_back(self, timeout: Optional[int] = None, wait_until: str = "load") -> bool:
        """
        后退到上一页
        :param timeout: 超时时间
        :param wait_until: 等待条件
        :return: 后退是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            self.page.go_back(timeout=timeout, wait_until=wait_until)
            self.logger.info("成功后退到上一页")
            return True
        except Exception as e:
            self.logger.error(f"后退到上一页失败: {str(e)}")
            return False
    
    def go_forward(self, timeout: Optional[int] = None, wait_until: str = "load") -> bool:
        """
        前进到下一页
        :param timeout: 超时时间
        :param wait_until: 等待条件
        :return: 前进是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            self.page.go_forward(timeout=timeout, wait_until=wait_until)
            self.logger.info("成功前进到下一页")
            return True
        except Exception as e:
            self.logger.error(f"前进到下一页失败: {str(e)}")
            return False
    
    def get_url(self) -> str:
        """
        获取当前页面URL
        :return: 当前URL
        """
        url = self.page.url
        self.logger.info(f"当前页面URL: {url}")
        return url
    
    def get_title(self) -> str:
        """
        获取页面标题
        :return: 页面标题
        """
        title = self.page.title()
        self.logger.info(f"页面标题: {title}")
        return title
    
    def close_page(self) -> bool:
        """
        关闭页面
        :return: 关闭是否成功
        """
        try:
            self.page.close()
            self.logger.info("成功关闭页面")
            return True
        except Exception as e:
            self.logger.error(f"关闭页面失败: {str(e)}")
            return False
    
    # ================== JavaScript执行相关方法 ==================
    
    def execute_javascript(self, script: str, *args) -> Any:
        """
        执行JavaScript代码
        :param script: JavaScript代码
        :param args: 传递给脚本的参数
        :return: 脚本执行结果
        """
        try:
            result = self.page.evaluate(script, *args)
            self.logger.info(f"成功执行JavaScript: {script[:50]}...")
            return result
        except Exception as e:
            self.logger.error(f"执行JavaScript失败: {str(e)}")
            return None
    
    def scroll_to_element(self, selector: str, timeout: Optional[int] = None) -> bool:
        """
        滚动到指定元素
        :param selector: 元素选择器
        :param timeout: 超时时间
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            locator.scroll_into_view_if_needed(timeout=timeout)
            self.logger.info(f"成功滚动到元素: {selector}")
            return True
        except Exception as e:
            self.logger.error(f"滚动到元素失败 {selector}: {str(e)}")
            return False
    
    def scroll_to_top(self) -> bool:
        """
        滚动到页面顶部
        :return: 操作是否成功
        """
        try:
            self.execute_javascript("() => window.scrollTo(0, 0)")
            self.logger.info("成功滚动到页面顶部")
            return True
        except Exception as e:
            self.logger.error(f"滚动到页面顶部失败: {str(e)}")
            return False
    
    def scroll_to_bottom(self) -> bool:
        """
        滚动到页面底部
        :return: 操作是否成功
        """
        try:
            self.execute_javascript("() => window.scrollTo(0, document.body.scrollHeight)")
            self.logger.info("成功滚动到页面底部")
            return True
        except Exception as e:
            self.logger.error(f"滚动到页面底部失败: {str(e)}")
            return False
    
    def scroll_by_pixel(self, x: int = 0, y: int = 0, smooth: bool = True, 
                       check_result: bool = True) -> bool:
        """
        按像素滚动页面（改进版）
        :param x: 水平滚动像素（正数向右，负数向左）
        :param y: 垂直滚动像素（正数向下，负数向上）
        :param smooth: 是否使用平滑滚动
        :param check_result: 是否检查滚动结果
        :return: 操作是否成功
        """
        try:
            # 获取滚动前的位置
            before_position = self.get_scroll_position() if check_result else {"x": 0, "y": 0}
            
            # 检查页面是否可滚动
            can_scroll = self.execute_javascript("""() => {
                return {
                    canScrollVertically: document.body.scrollHeight > window.innerHeight,
                    canScrollHorizontally: document.body.scrollWidth > window.innerWidth,
                    scrollHeight: document.body.scrollHeight,
                    windowHeight: window.innerHeight,
                    currentY: window.pageYOffset
                }
            }""")
            
            if not can_scroll.get('canScrollVertically', True) and y != 0:
                self.logger.warning("页面内容高度不够，无法垂直滚动")
            
            if not can_scroll.get('canScrollHorizontally', True) and x != 0:
                self.logger.warning("页面内容宽度不够，无法水平滚动")
            
            # 使用多种方法尝试滚动
            scroll_methods = []
            
            if smooth:
                # 方法1: 平滑滚动
                script1 = f"""
                    window.scrollBy({{
                        left: {x},
                        top: {y},
                        behavior: 'smooth'
                    }});
                """
                scroll_methods.append(("平滑滚动", script1))
            
            # 方法2: 直接滚动
            script2 = f"window.scrollBy({x}, {y});"
            scroll_methods.append(("直接滚动", script2))
            
            # 方法3: 使用 scrollTo 的相对滚动
            script3 = f"""
                window.scrollTo({{
                    left: window.pageXOffset + {x},
                    top: window.pageYOffset + {y},
                    behavior: '{'smooth' if smooth else 'auto'}'
                }});
            """
            scroll_methods.append(("scrollTo滚动", script3))
            
            # 尝试滚动方法
            success = False
            for method_name, script in scroll_methods:
                try:
                    self.execute_javascript(script)
                    
                    # 给平滑滚动一些时间
                    if smooth:
                        time.sleep(0.5)
                    
                    # 检查是否成功滚动
                    if check_result:
                        after_position = self.get_scroll_position()
                        
                        # 检查是否有实际滚动发生
                        scroll_diff_x = abs(after_position['x'] - before_position['x'])
                        scroll_diff_y = abs(after_position['y'] - before_position['y'])
                        
                        if (x != 0 and scroll_diff_x > 0) or (y != 0 and scroll_diff_y > 0) or (x == 0 and y == 0):
                            self.logger.info(f"使用{method_name}成功滚动页面 x={x}, y={y} 像素")
                            self.logger.info(f"滚动前位置: {before_position}, 滚动后位置: {after_position}")
                            success = True
                            break
                    else:
                        self.logger.info(f"使用{method_name}执行滚动 x={x}, y={y} 像素")
                        success = True
                        break
                        
                except Exception as method_error:
                    self.logger.warning(f"{method_name}失败: {str(method_error)}")
                    continue
            
            if not success:
                # 最后尝试：使用 Playwright 原生滚动
                try:
                    self.page.mouse.wheel(x, y)
                    self.logger.info(f"使用Playwright原生滚动 x={x}, y={y} 像素")
                    success = True
                except Exception as e:
                    self.logger.error(f"Playwright原生滚动也失败: {str(e)}")
            
            return success
            
        except Exception as e:
            self.logger.error(f"按像素滚动失败: {str(e)}")
            return False
    
    def check_page_scrollable(self) -> Dict[str, Any]:
        """
        检查页面是否可滚动
        :return: 页面滚动信息
        """
        try:
            scroll_info = self.execute_javascript("""() => {
                return {
                    canScrollVertically: document.body.scrollHeight > window.innerHeight,
                    canScrollHorizontally: document.body.scrollWidth > window.innerWidth,
                    scrollHeight: document.body.scrollHeight,
                    scrollWidth: document.body.scrollWidth,
                    windowHeight: window.innerHeight,
                    windowWidth: window.innerWidth,
                    currentScrollY: window.pageYOffset,
                    currentScrollX: window.pageXOffset,
                    maxScrollY: document.body.scrollHeight - window.innerHeight,
                    maxScrollX: document.body.scrollWidth - window.innerWidth
                }
            }""")
            self.logger.info(f"页面滚动信息: {scroll_info}")
            return scroll_info or {}
        except Exception as e:
            self.logger.error(f"检查页面滚动性失败: {str(e)}")
            return {}
    
    def scroll_with_retry(self, x: int = 0, y: int = 0, max_attempts: int = 3) -> bool:
        """
        带重试的滚动方法
        :param x: 水平滚动像素
        :param y: 垂直滚动像素  
        :param max_attempts: 最大重试次数
        :return: 操作是否成功
        """
        for attempt in range(max_attempts):
            self.logger.info(f"滚动尝试 {attempt + 1}/{max_attempts}")
            
            # 先检查页面状态
            if attempt == 0:
                scroll_info = self.check_page_scrollable()
                if not scroll_info.get('canScrollVertically', True) and y != 0:
                    self.logger.warning("页面不可垂直滚动")
                    return False
                if not scroll_info.get('canScrollHorizontally', True) and x != 0:
                    self.logger.warning("页面不可水平滚动")
                    return False
            
            # 尝试不同的滚动方法
            methods = [
                ("JavaScript scrollBy", lambda: self._js_scroll_by(x, y)),
                ("Playwright wheel", lambda: self._playwright_wheel_scroll(x, y)),
                ("Keyboard scroll", lambda: self._keyboard_scroll(y)),
            ]
            
            for method_name, scroll_func in methods:
                try:
                    before_pos = self.get_scroll_position()
                    scroll_func()
                    time.sleep(0.5)  # 等待滚动完成
                    after_pos = self.get_scroll_position()
                    
                    # 检查是否成功滚动
                    if (y != 0 and abs(after_pos['y'] - before_pos['y']) > 10) or \
                       (x != 0 and abs(after_pos['x'] - before_pos['x']) > 10):
                        self.logger.info(f"使用{method_name}成功滚动")
                        return True
                        
                except Exception as e:
                    self.logger.warning(f"{method_name}失败: {str(e)}")
                    continue
            
            # 如果所有方法都失败，等待后重试
            if attempt < max_attempts - 1:
                time.sleep(1)
        
        self.logger.error(f"所有滚动方法都失败，尝试了{max_attempts}次")
        return False
    
    def _js_scroll_by(self, x: int, y: int):
        """JavaScript scrollBy方法"""
        self.execute_javascript(f"window.scrollBy({x}, {y})")
    
    def _playwright_wheel_scroll(self, x: int, y: int):
        """Playwright鼠标滚轮方法"""
        self.page.mouse.wheel(x, y)
    
    def _keyboard_scroll(self, y: int):
        """键盘滚动方法"""
        if y > 0:
            # 向下滚动
            for _ in range(abs(y) // 100):
                self.page.keyboard.press("PageDown")
                time.sleep(0.1)
        elif y < 0:
            # 向上滚动
            for _ in range(abs(y) // 100):
                self.page.keyboard.press("PageUp")
                time.sleep(0.1)
    
    def scroll_to_position(self, x: int = 0, y: int = 0) -> bool:
        """
        滚动到指定位置
        :param x: 水平位置
        :param y: 垂直位置
        :return: 操作是否成功
        """
        try:
            self.execute_javascript(f"window.scrollTo({x}, {y})")
            self.logger.info(f"成功滚动到位置 x={x}, y={y}")
            return True
        except Exception as e:
            self.logger.error(f"滚动到指定位置失败: {str(e)}")
            return False
    
    def scroll_element_into_view(self, selector: str, timeout: Optional[int] = None, 
                                align_to_top: bool = True) -> bool:
        """
        滚动元素到视图中
        :param selector: 元素选择器
        :param timeout: 超时时间
        :param align_to_top: 是否对齐到顶部（True）或底部（False）
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)
            
            # 使用JavaScript的scrollIntoView方法
            script = f"""() => {{
            const element = document.querySelector('{selector}');
            if (element) {{
                element.scrollIntoView({{ 
                    behavior: 'smooth', 
                    block: '{'start' if align_to_top else 'end'}',
                    inline: 'nearest'
                }});
            }}
            }}"""
            self.execute_javascript(script)
            self.logger.info(f"成功滚动元素到视图中: {selector}")
            return True
        except Exception as e:
            self.logger.error(f"滚动元素到视图中失败 {selector}: {str(e)}")
            return False
    
    def scroll_element_by_pixel(self, selector: str, x: int = 0, y: int = 0, 
                               timeout: Optional[int] = None) -> bool:
        """
        滚动指定元素内容
        :param selector: 可滚动元素选择器
        :param x: 水平滚动像素
        :param y: 垂直滚动像素
        :param timeout: 超时时间
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            script = f"""() => {{
            const element = document.querySelector('{selector}');
            if (element) {{
                element.scrollBy({x}, {y});
            }}
            }}"""
            self.execute_javascript(script)
            self.logger.info(f"成功滚动元素 {selector} x={x}, y={y} 像素")
            return True
        except Exception as e:
            self.logger.error(f"滚动元素失败 {selector}: {str(e)}")
            return False
    
    def scroll_element_to_position(self, selector: str, x: int = 0, y: int = 0, 
                                  timeout: Optional[int] = None) -> bool:
        """
        滚动指定元素到指定位置
        :param selector: 可滚动元素选择器
        :param x: 水平位置
        :param y: 垂直位置
        :param timeout: 超时时间
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            script = f"""() => {{
            const element = document.querySelector('{selector}');
            if (element) {{
                element.scrollTo({x}, {y});
            }}
            }}"""
            self.execute_javascript(script)
            self.logger.info(f"成功滚动元素 {selector} 到位置 x={x}, y={y}")
            return True
        except Exception as e:
            self.logger.error(f"滚动元素到指定位置失败 {selector}: {str(e)}")
            return False
    
    def scroll_element_to_top(self, selector: str, timeout: Optional[int] = None) -> bool:
        """
        滚动指定元素到顶部
        :param selector: 可滚动元素选择器
        :param timeout: 超时时间
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            script = f"""() => {{
            const element = document.querySelector('{selector}');
            if (element) {{
                element.scrollTo(0, 0);
            }}
            }}"""
            self.execute_javascript(script)
            self.logger.info(f"成功滚动元素 {selector} 到顶部")
            return True
        except Exception as e:
            self.logger.error(f"滚动元素到顶部失败 {selector}: {str(e)}")
            return False
    
    def scroll_element_to_bottom(self, selector: str, timeout: Optional[int] = None) -> bool:
        """
        滚动指定元素到底部
        :param selector: 可滚动元素选择器
        :param timeout: 超时时间
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            script = f"""() => {{
            const element = document.querySelector('{selector}');
            if (element) {{
                element.scrollTo(0, element.scrollHeight);
            }}
            }}"""
            self.execute_javascript(script)
            self.logger.info(f"成功滚动元素 {selector} 到底部")
            return True
        except Exception as e:
            self.logger.error(f"滚动元素到底部失败 {selector}: {str(e)}")
            return False
    
    def get_scroll_position(self) -> Dict[str, int]:
        """
        获取页面当前滚动位置
        :return: 滚动位置字典 {"x": 水平位置, "y": 垂直位置}
        """
        try:
            result = self.execute_javascript("""() => {
                return {
                    x: window.pageXOffset || document.documentElement.scrollLeft,
                    y: window.pageYOffset || document.documentElement.scrollTop
                }
            }""")
            self.logger.info(f"页面滚动位置: {result}")
            return result or {"x": 0, "y": 0}
        except Exception as e:
            self.logger.error(f"获取页面滚动位置失败: {str(e)}")
            return {"x": 0, "y": 0}
    
    def get_element_scroll_position(self, selector: str, timeout: Optional[int] = None) -> Dict[str, int]:
        """
        获取指定元素的滚动位置
        :param selector: 元素选择器
        :param timeout: 超时时间
        :return: 滚动位置字典 {"x": 水平位置, "y": 垂直位置}
        """
        try:
            timeout = timeout or self.default_timeout
            script = f"""() => {{
            const element = document.querySelector('{selector}');
            if (element) {{
                return {{
                    x: element.scrollLeft,
                    y: element.scrollTop
                }};
            }}
            return {{x: 0, y: 0}};
            }}"""
            result = self.execute_javascript(script)
            self.logger.info(f"元素 {selector} 滚动位置: {result}")
            return result or {"x": 0, "y": 0}
        except Exception as e:
            self.logger.error(f"获取元素滚动位置失败 {selector}: {str(e)}")
            return {"x": 0, "y": 0}
    
    def smooth_scroll_to_element(self, selector: str, timeout: Optional[int] = None,
                                offset_y: int = 0, offset_x: int = 0) -> bool:
        """
        平滑滚动到元素位置
        :param selector: 元素选择器
        :param timeout: 超时时间
        :param offset_y: 垂直偏移（负数向上，正数向下）
        :param offset_x: 水平偏移（负数向左，正数向右）
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            script = f"""() => {{
            const element = document.querySelector('{selector}');
            if (element) {{
                const rect = element.getBoundingClientRect();
                const targetY = window.pageYOffset + rect.top + {offset_y};
                const targetX = window.pageXOffset + rect.left + {offset_x};
                
                window.scrollTo({{
                    top: targetY,
                    left: targetX,
                    behavior: 'smooth'
                }});
                return true;
            }}
            return false;
            }}"""
            result = self.execute_javascript(script)
            if result:
                self.logger.info(f"成功平滑滚动到元素: {selector}")
                return True
            else:
                self.logger.error(f"元素不存在: {selector}")
                return False
        except Exception as e:
            self.logger.error(f"平滑滚动到元素失败 {selector}: {str(e)}")
            return False
    
    def scroll_page_percentage(self, percentage: float) -> bool:
        """
        滚动到页面指定百分比位置
        :param percentage: 百分比 (0.0-1.0)，0为顶部，1为底部
        :return: 操作是否成功
        """
        try:
            if not 0 <= percentage <= 1:
                raise ValueError("百分比必须在0.0到1.0之间")
            
            script = f"""() => {{
            const maxScroll = document.body.scrollHeight - window.innerHeight;
            const targetScroll = maxScroll * {percentage};
            window.scrollTo({{
                top: targetScroll,
                behavior: 'smooth'
            }});
            }}"""
            self.execute_javascript(script)
            self.logger.info(f"成功滚动到页面 {percentage*100}% 位置")
            return True
        except Exception as e:
            self.logger.error(f"滚动到百分比位置失败: {str(e)}")
            return False
    
    def scroll_element_percentage(self, selector: str, percentage: float, 
                                 timeout: Optional[int] = None) -> bool:
        """
        滚动指定元素到百分比位置
        :param selector: 元素选择器
        :param percentage: 百分比 (0.0-1.0)
        :param timeout: 超时时间
        :return: 操作是否成功
        """
        try:
            if not 0 <= percentage <= 1:
                raise ValueError("百分比必须在0.0到1.0之间")
            
            timeout = timeout or self.default_timeout
            script = f"""() => {{
            const element = document.querySelector('{selector}');
            if (element) {{
                const maxScroll = element.scrollHeight - element.clientHeight;
                const targetScroll = maxScroll * {percentage};
                element.scrollTo({{
                    top: targetScroll,
                    behavior: 'smooth'
                }});
                return true;
            }}
            return false;
            }}"""
            result = self.execute_javascript(script)
            if result:
                self.logger.info(f"成功滚动元素 {selector} 到 {percentage*100}% 位置")
                return True
            else:
                self.logger.error(f"元素不存在: {selector}")
                return False
        except Exception as e:
            self.logger.error(f"滚动元素到百分比位置失败 {selector}: {str(e)}")
            return False
    
    def scroll_container_element(self, distance: int = 500, 
                                container_selector: str = None, 
                                timeout: Optional[int] = None) -> bool:
        """
        滚动容器元素（专门解决爱奇艺等特殊页面的滚动问题）
        :param distance: 滚动距离（正数向下，负数向上）
        :param container_selector: 容器选择器，如果不提供则自动查找
        :param timeout: 超时时间
        :return: 操作是否成功
        """
        try:
            timeout = timeout or self.default_timeout
            
            # 如果没有提供容器选择器，自动查找可滚动的容器
            if not container_selector:
                find_container_script = """() => {
                    // 查找真正可滚动的容器元素
                    const candidates = Array.from(document.querySelectorAll('div')).filter(el => {
                        const style = window.getComputedStyle(el);
                        const hasScroll = el.scrollHeight > el.clientHeight;
                        const canScroll = style.overflowY === 'auto' || 
                                        style.overflow === 'auto' || 
                                        style.overflow === 'hidden auto' ||
                                        style.overflowY === 'scroll';
                        return hasScroll && canScroll;
                    });
                    
                    if (candidates.length > 0) {
                        // 返回最大的可滚动容器
                        const largestContainer = candidates.reduce((max, current) => 
                            current.scrollHeight > max.scrollHeight ? current : max
                        );
                        
                        // 为元素添加临时ID以便后续查找
                        const tempId = 'temp_scrollable_' + Date.now();
                        largestContainer.setAttribute('data-temp-scroll-id', tempId);
                        
                        return {
                            found: true,
                            tempId: tempId,
                            scrollHeight: largestContainer.scrollHeight,
                            clientHeight: largestContainer.clientHeight,
                            currentScroll: largestContainer.scrollTop
                        };
                    }
                    
                    return { found: false };
                }"""
                
                container_info = self.execute_javascript(find_container_script)
                
                if not container_info or not container_info.get('found'):
                    self.logger.error("未找到可滚动的容器元素")
                    return False
                
                # 使用临时ID作为选择器
                container_selector = f"[data-temp-scroll-id='{container_info['tempId']}']"
                self.logger.info(f"找到可滚动容器: scrollHeight={container_info['scrollHeight']}, "
                               f"clientHeight={container_info['clientHeight']}")
            
            # 执行容器滚动
            scroll_script = f"""() => {{
                const container = document.querySelector('[data-temp-scroll-id]');
                if (!container) {{
                    return {{ success: false, error: '容器元素不存在' }};
                }}
                
                const beforeScroll = container.scrollTop;
                container.scrollTop += {distance};
                const afterScroll = container.scrollTop;
                
                // 清理临时属性
                container.removeAttribute('data-temp-scroll-id');
                
                return {{
                    success: true,
                    beforeScroll: beforeScroll,
                    afterScroll: afterScroll,
                    actualDistance: afterScroll - beforeScroll,
                    maxScroll: container.scrollHeight - container.clientHeight
                }};
            }}"""
            
            result = self.execute_javascript(scroll_script)
            
            if result and result.get('success'):
                actual_distance = result.get('actualDistance', 0)
                self.logger.info(f"容器滚动成功: 请求距离={distance}, 实际距离={actual_distance}, "
                               f"滚动位置={result.get('beforeScroll')} -> {result.get('afterScroll')}")
                return abs(actual_distance) > 0
            else:
                error_msg = result.get('error', '未知错误') if result else '滚动执行失败'
                self.logger.error(f"容器滚动失败: {error_msg}")
                return False
                
        except Exception as e:
            self.logger.error(f"容器滚动异常: {str(e)}")
            return False
    
    def scroll_iqiyi_page(self, distance: int = 500, smooth: bool = True, duration: int = 800) -> bool:
        """
        专门针对爱奇艺页面的滚动方法（支持平滑滚动）
        :param distance: 滚动距离（正数向下，负数向上）
        :param smooth: 是否使用平滑滚动
        :param duration: 平滑滚动持续时间（毫秒）
        :return: 操作是否成功
        """
        try:
            # 爱奇艺页面的特殊滚动逻辑
            scroll_script = f"""() => {{
                // 查找爱奇艺页面的主容器
                let scrollableElement = null;
                
                // 尝试多种选择器找到正确的滚动容器
                const selectors = [
                    'div[style*="overflow: hidden auto"]',
                    'div[style*="overflow-y: auto"]',
                    '.van-config-provider',
                    'body > div:first-child'
                ];
                
                for (const selector of selectors) {{
                    const el = document.querySelector(selector);
                    if (el && el.scrollHeight > el.clientHeight) {{
                        const style = window.getComputedStyle(el);
                        if (style.overflowY === 'auto' || style.overflow === 'hidden auto') {{
                            scrollableElement = el;
                            break;
                        }}
                    }}
                }}
                
                // 如果还没找到，手动查找最大的可滚动div
                if (!scrollableElement) {{
                    const divs = Array.from(document.querySelectorAll('div'));
                    scrollableElement = divs.find(div => {{
                        const style = window.getComputedStyle(div);
                        return div.scrollHeight > div.clientHeight && 
                               (style.overflowY === 'auto' || style.overflow === 'hidden auto');
                    }});
                }}
                
                if (scrollableElement) {{
                    const beforeScroll = scrollableElement.scrollTop;
                    const targetScroll = beforeScroll + {distance};
                    
                    // 边界检查
                    const maxScroll = scrollableElement.scrollHeight - scrollableElement.clientHeight;
                    const finalTarget = Math.max(0, Math.min(targetScroll, maxScroll));
                    
                    if ({str(smooth).lower()}) {{
                        // 平滑滚动实现
                        return new Promise((resolve) => {{
                            const duration = {duration};
                            const startTime = Date.now();
                            const startScroll = beforeScroll;
                            const scrollDistance = finalTarget - startScroll;
                            
                            // 缓动函数 (easeInOutCubic)
                            function easeInOutCubic(t) {{
                                return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
                            }}
                            
                            function animateScroll() {{
                                const currentTime = Date.now();
                                const elapsed = currentTime - startTime;
                                const progress = Math.min(elapsed / duration, 1);
                                
                                const easedProgress = easeInOutCubic(progress);
                                const currentScroll = startScroll + scrollDistance * easedProgress;
                                
                                scrollableElement.scrollTop = currentScroll;
                                
                                if (progress < 1) {{
                                    requestAnimationFrame(animateScroll);
                                }} else {{
                                    // 动画完成
                                    const finalScroll = scrollableElement.scrollTop;
                                    resolve({{
                                        success: true,
                                        beforeScroll: beforeScroll,
                                        afterScroll: finalScroll,
                                        actualDistance: finalScroll - beforeScroll,
                                        targetDistance: {distance},
                                        smooth: true,
                                        duration: elapsed,
                                        elementInfo: {{
                                            tagName: scrollableElement.tagName,
                                            className: scrollableElement.className,
                                            scrollHeight: scrollableElement.scrollHeight,
                                            clientHeight: scrollableElement.clientHeight
                                        }}
                                    }});
                                }}
                            }}
                            
                            animateScroll();
                        }});
                    }} else {{
                        // 直接滚动
                        scrollableElement.scrollTop = finalTarget;
                        const afterScroll = scrollableElement.scrollTop;
                        
                        return {{
                            success: true,
                            beforeScroll: beforeScroll,
                            afterScroll: afterScroll,
                            actualDistance: afterScroll - beforeScroll,
                            targetDistance: {distance},
                            smooth: false,
                            elementInfo: {{
                                tagName: scrollableElement.tagName,
                                className: scrollableElement.className,
                                scrollHeight: scrollableElement.scrollHeight,
                                clientHeight: scrollableElement.clientHeight
                            }}
                        }};
                    }}
                }} else {{
                    return {{
                        success: false,
                        error: '未找到可滚动的爱奇艺容器'
                    }};
                }}
            }}"""
            
            result = self.execute_javascript(scroll_script)
            
            # 如果是平滑滚动，需要等待动画完成
            if smooth and result and result.get('success'):
                import time
                time.sleep(duration / 1000 + 0.1)  # 等待动画完成
            
            if result and result.get('success'):
                actual_distance = result.get('actualDistance', 0)
                target_distance = result.get('targetDistance', distance)
                element_info = result.get('elementInfo', {})
                smooth_info = "平滑" if result.get('smooth', False) else "直接"
                duration_info = f"({result.get('duration', 0)}ms)" if result.get('smooth', False) else ""
                
                self.logger.info(f"爱奇艺页面{smooth_info}滚动成功{duration_info}: "
                               f"目标距离={target_distance}, 实际距离={actual_distance}, "
                               f"容器={element_info.get('tagName', 'unknown')} "
                               f"({element_info.get('scrollHeight', 0)}x{element_info.get('clientHeight', 0)})")
                return abs(actual_distance) > 0
            else:
                error_msg = result.get('error', '滚动失败') if result else '脚本执行失败'
                self.logger.error(f"爱奇艺页面滚动失败: {error_msg}")
                return False
                
        except Exception as e:
            self.logger.error(f"爱奇艺页面滚动异常: {str(e)}")
            return False
    
    def smart_scroll_page(self, distance: int = 500, page_type: str = 'auto', 
                         smooth: bool = True, duration: int = 800) -> bool:
        """
        智能页面滚动（自动检测页面类型并选择最佳滚动方法，支持平滑滚动）
        :param distance: 滚动距离（正数向下，负数向上）
        :param page_type: 页面类型 ('auto', 'iqiyi', 'normal')
        :param smooth: 是否使用平滑滚动
        :param duration: 平滑滚动持续时间（毫秒）
        :return: 操作是否成功
        """
        try:
            current_url = self.get_url()
            
            # 自动检测页面类型
            if page_type == 'auto':
                if 'iqiyi.com' in current_url:
                    page_type = 'iqiyi'
                else:
                    page_type = 'normal'
            
            # 根据页面类型选择滚动方法
            if page_type == 'iqiyi':
                self.logger.info(f"检测到爱奇艺页面，使用专用{'平滑' if smooth else '直接'}滚动方法")
                return self.scroll_iqiyi_page(distance, smooth=smooth, duration=duration)
            else:
                # 尝试多种方法
                methods = [
                    ("标准页面滚动", lambda: self.scroll_by_pixel(0, distance, smooth=smooth, check_result=True)),
                    ("容器元素滚动", lambda: self.scroll_container_element(distance)),
                    ("重试滚动", lambda: self.scroll_with_retry(0, distance, max_attempts=2))
                ]
                
                for method_name, method_func in methods:
                    self.logger.info(f"尝试{method_name}")
                    if method_func():
                        self.logger.info(f"{method_name}成功")
                        return True
                
                self.logger.error("所有滚动方法都失败了")
                return False
                
        except Exception as e:
            self.logger.error(f"智能滚动异常: {str(e)}")
            return False
    
    # ================== 本地存储相关方法 ==================
    
    def get_local_storage(self, key: str) -> Optional[str]:
        """
        获取localStorage中的值
        :param key: 键名
        :return: 对应的值，不存在返回None
        """
        try:
            value = self.execute_javascript(f"() => localStorage.getItem('{key}')")
            self.logger.info(f"成功获取localStorage {key}: {value}")
            return value
        except Exception as e:
            self.logger.error(f"获取localStorage失败 {key}: {str(e)}")
            return None
    
    def set_local_storage(self, key: str, value: str) -> bool:
        """
        设置localStorage中的值
        :param key: 键名
        :param value: 值
        :return: 操作是否成功
        """
        try:
            self.execute_javascript(f"() => localStorage.setItem('{key}', '{value}')")
            self.logger.info(f"成功设置localStorage {key}: {value}")
            return True
        except Exception as e:
            self.logger.error(f"设置localStorage失败 {key}: {str(e)}")
            return False
    
    def clear_local_storage(self) -> bool:
        """
        清空localStorage
        :return: 操作是否成功
        """
        try:
            self.execute_javascript("() => localStorage.clear()")
            self.logger.info("成功清空localStorage")
            return True
        except Exception as e:
            self.logger.error(f"清空localStorage失败: {str(e)}")
            return False
    
    # ================== Cookie相关方法 ==================
    
    def get_cookies(self) -> List[Dict[str, Any]]:
        """
        获取所有cookies
        :return: cookies列表
        """
        try:
            cookies = self.page.context.cookies()
            self.logger.info(f"成功获取cookies，数量: {len(cookies)}")
            return cookies
        except Exception as e:
            self.logger.error(f"获取cookies失败: {str(e)}")
            return []
    
    def add_cookie(self, cookie: Dict[str, Any]) -> bool:
        """
        添加cookie
        :param cookie: cookie字典，必须包含name和value
        :return: 操作是否成功
        """
        try:
            self.page.context.add_cookies([cookie])
            self.logger.info(f"成功添加cookie: {cookie.get('name', 'unknown')}")
            return True
        except Exception as e:
            self.logger.error(f"添加cookie失败: {str(e)}")
            return False
    
    def clear_cookies(self) -> bool:
        """
        清空所有cookies
        :return: 操作是否成功
        """
        try:
            self.page.context.clear_cookies()
            self.logger.info("成功清空所有cookies")
            return True
        except Exception as e:
            self.logger.error(f"清空cookies失败: {str(e)}")
            return False

    def click_element_by_percentage(self, selector: str, x_percent: float, y_percent: float = 0.5,
                                    timeout: Optional[int] = None,
                                    img_desc: Optional[str] = None, auto_screenshot: bool = True) -> bool:
        """
        按元素范围内的百分比位置点击。
        :param selector: 元素选择器
        :param x_percent: 横向百分比（0.0-1.0），0为最左，1为最右
        :param y_percent: 纵向百分比（0.0-1.0），0为最上，1为最下，默认0.5居中
        :param timeout: 超时时间
        :param img_desc: 截图描述
        :param auto_screenshot: 失败是否截图
        :return: 是否点击成功
        """
        try:
            if not 0.0 <= x_percent <= 1.0 or not 0.0 <= y_percent <= 1.0:
                raise ValueError("百分比必须在0.0到1.0之间")

            timeout = timeout or self.default_timeout
            locator = self.get_locator(selector)

            # 确保元素在视口内，避免获取不到边界
            try:
                locator.scroll_into_view_if_needed(timeout=timeout)
            except Exception:
                pass

            box = locator.bounding_box(timeout=timeout)
            if not box:
                raise Exception(f"无法获取元素边界框: {selector}")

            target_x = box['x'] + max(0, min(1, x_percent)) * box['width']
            target_y = box['y'] + max(0, min(1, y_percent)) * box['height']

            # 移动并点击
            self.page.mouse.move(target_x, target_y)
            self.page.mouse.click(target_x, target_y)

            self.logger.info(f"按百分比点击元素 {selector}: x={x_percent*100:.1f}%, y={y_percent*100:.1f}% → 坐标({target_x:.1f},{target_y:.1f})")
            return True
        except Exception as e:
            self.logger.error(f"按百分比点击元素失败 {selector}: {str(e)}")
            if auto_screenshot:
                desc = img_desc or f"click_by_percent_failed_{selector.replace(' ', '_').replace('>', '_')}"
                self.save_img(desc)
            return False

    def click_viewport_center(self, times: int = 1, wait: float = 0.1) -> None:
        """
        点击视口中心位置，用于唤起视频播放控件等场景。
        :param times: 点击次数
        :param wait: 每次点击后的等待秒数
        """
        try:
            size = self.page.viewport_size or self.execute_javascript("() => ({width: window.innerWidth, height: window.innerHeight})")
            width = float(size.get('width', 0))
            height = float(size.get('height', 0))
            if width <= 0 or height <= 0:
                raise Exception("无法获取视口尺寸")
            center_x = width / 2
            center_y = height / 2
            for i in range(max(1, int(times))):
                self.page.mouse.click(center_x, center_y)
                if i < times - 1:
                    time.sleep(max(0.0, float(wait)))
            self.logger.info(f"点击视口中心完成，次数={times}，位置=({center_x:.1f},{center_y:.1f})")
        except Exception as e:
            self.logger.warning(f"点击视口中心失败: {str(e)}")

    def ensure_element_visible_with_center_wakeup(self, selector: str, tries: int = 2,
                                                  center_clicks: int = 1, wait_after_click: float = 0.2,
                                                  check_timeout_ms: int = 500) -> bool:
        """
        确保元素可见；若不可见，点击屏幕中心唤起后再检查。
        :param selector: 元素选择器
        :param tries: 唤起重试轮数
        :param center_clicks: 每轮点击中心次数
        :param wait_after_click: 唤起点击后的等待秒数
        :param check_timeout_ms: 每次可见性检查的超时（毫秒）
        :return: 元素是否最终可见
        """
        try:
            if self.is_element_visible(selector, timeout=check_timeout_ms):
                return True
            for _ in range(max(1, int(tries))):
                self.click_viewport_center(times=center_clicks, wait=wait_after_click)
                time.sleep(wait_after_click)
                if self.is_element_visible(selector, timeout=check_timeout_ms):
                    return True
            return False
        except Exception as e:
            self.logger.warning(f"唤起控件检查失败 {selector}: {str(e)}")
            return False
